package com.platform.common.redis;

import com.platform.common.redis.hash.HashRedisUtil;
import org.apache.commons.lang3.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;
import redis.clients.jedis.Tuple;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Redis工具类
 *
 * @author LiangMingHui
 */
public class RedisUtil implements RedisService {

    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

    private static final RedisUtil instance = new RedisUtil();

    RedisService redisService = null;

    public static RedisUtil getInstance() {
        return instance;
    }

    private RedisUtil() {
        redisService = HashRedisUtil.getInstance();
    }

    @Override
    public String set(String key, String value) {
        return redisService.set(key, value);
    }

    @Override
    public String set(String key, String value, String nxxx, String expx, long time) {
        return redisService.set(key, value, nxxx, expx, time);
    }

    @Override
    public String get(String key) {
        return redisService.get(key);
    }

    @Override
    public Boolean exists(String key) {
        return redisService.exists(key);
    }

    @Override
    public Long setnx(String key, String value) {
        return redisService.setnx(key, value);
    }

    @Override
    public String setex(String key, int seconds, String value) {
        return redisService.setex(key, seconds, value);
    }

    @Override
    public Long expire(String key, int seconds) {
        return redisService.expire(key, seconds);
    }

    @Override
    public Long incr(String key) {
        return redisService.incr(key);
    }

    @Override
    public Long decr(String key) {
        return redisService.decr(key);
    }

    @Override
    public Long hset(String key, String field, String value) {
        return redisService.hset(key, field, value);
    }

    @Override
    public String hget(String key, String field) {
        return redisService.hget(key, field);
    }

    @Override
    public String hmset(String key, Map<String, String> hash) {
        return redisService.hmset(key, hash);
    }

    @Override
    public List<String> hmget(String key, String... fields) {
        return redisService.hmget(key, fields);
    }

    @Override
    public Long del(String key) {
        return redisService.del(key);
    }

    @Override
    public Map<String, String> hgetAll(String key) {
        return redisService.hgetAll(key);
    }

    @Override
    public void destroy() {
        redisService.destroy();
    }

    @Override
    public Object getObject(String key) {
        return redisService.getObject(key);
    }

    @Override
    public Object setObject(String cacheKey, Object value, int expireTime) {
        return redisService.setObject(cacheKey, value, expireTime);
    }

    @Override
    public List<Object> keysObject(String string) {
        return redisService.keysObject(string);
    }

    /**
     * redis加锁参数
     *
     * @author LiangMingHui
     */
    public static class LockData {

        private static final int MAX_HEARTBEAT_SECONDS = 30;

        private String key;
        private int expireSeconds = 10;
        private int tryCount = 100;
        private int tryDelayMs = 100;
        private long lastActivity = 0;
        private int heartBeatMillisSeconds;

        public LockData(String key) {
            this.key = key;
        }

        public LockData(String key, int expireSeconds) {
            this.key = key;
            this.expireSeconds = expireSeconds;
            heartBeatMillisSeconds = Math.min(MAX_HEARTBEAT_SECONDS, expireSeconds / 2) * 1000;
        }

        public void clearLastActivity() {
            lastActivity = 0;
        }

        /**
         * 锁的键
         */
        public String getKey() {
            return key;
        }

        /**
         * 锁的键
         */
        public void setKey(String key) {
            this.key = key;
        }

        /**
         * 锁的过期秒数
         */
        public int getExpireSeconds() {
            return expireSeconds;
        }

        /**
         * 锁的过期秒数
         */
        public void setExpireSeconds(int expireSeconds) {
            this.expireSeconds = expireSeconds;
            heartBeatMillisSeconds = Math.min(MAX_HEARTBEAT_SECONDS, expireSeconds / 2) * 1000;
        }

        /**
         * 加锁时的重试次数
         */
        public int getTryCount() {
            return tryCount;
        }

        /**
         * 加锁时的重试次数
         */
        public void setTryCount(int tryCount) {
            this.tryCount = tryCount;
        }

        /**
         * 加锁重试前的等待毫秒数
         */
        public int getTryDelayMs() {
            return tryDelayMs;
        }

        /**
         * 加锁重试前的等待毫秒数
         */
        public void setTryDelayMs(int tryDelayMs) {
            this.tryDelayMs = tryDelayMs;
        }
    }

    /**
     * 加锁
     *
     * @param lockData
     * @return 1表示获取成功，其他表示获取失败
     * @throws InterruptedException 加锁重试前的等待时被中断
     */
    public Long lock(LockData lockData) throws InterruptedException {
        long l = System.currentTimeMillis() + (lockData.expireSeconds * 1000);
        int count = 0;
        Long ret;
        long startTime = 0;
        if (logger.isDebugEnabled()) {
            startTime = System.currentTimeMillis();
        }
        do {
            logger.info("RedisUtil.lock setnx key=" + lockData.key + " count=" + count + " tryCount=" + lockData.tryCount);
            ret = setnx(lockData.key, String.valueOf(l));
            logger.info("RedisUtil.lock setnx return: " + ret);
            if (ret != null && 1 == ret) {
                expire(lockData.key, lockData.expireSeconds);
                lockData.lastActivity = System.currentTimeMillis();
                break;
            } else {
                Thread.sleep(lockData.tryDelayMs + 10 * RandomUtils.nextInt(0, 5));
            }
        } while (++count <= lockData.tryCount);
        if (logger.isDebugEnabled()) {
            startTime = System.currentTimeMillis() - startTime;
            logger.debug("获取redis锁{} {}， count={}， time={}", lockData.key, ret == 1 ? "成功" : "失败", count, startTime);
        }

        return ret;
    }

    /**
     * 解锁
     *
     * @param lockData
     */
    public void unlock(LockData lockData) {
        del(lockData.key);
        lockData.lastActivity = 0;
        if (logger.isDebugEnabled()) {
            logger.debug("释放redis锁{}", lockData.key);
        }
    }

    /**
     * 更新锁的超时时间。加锁后，在长时间的操作中间，可以调用本函数来保证锁不过期
     *
     * @param lockData
     */
    public void lockHeartBeat(LockData lockData) {
        if (lockData.lastActivity > 0) {
            if (lockData.heartBeatMillisSeconds > 0 && System.currentTimeMillis() - lockData.lastActivity > lockData.heartBeatMillisSeconds) {
                return;
            }
        }
        expire(lockData.key, lockData.expireSeconds);
        lockData.lastActivity = System.currentTimeMillis();
    }

    @Override
    public ScanResult<String> scan(String cursor) {
        return redisService.scan(cursor);
    }

    @Override
    public ScanResult<String> scan(String cursor, ScanParams params) {
        return redisService.scan(cursor, params);
    }

    @Override
    public Long zadd(String key, double score, String member) {
        return redisService.zadd(key, score, member);
    }

    @Override
    public Long zadd(String key, Map<String, Double> scoreMembers) {
        return redisService.zadd(key, scoreMembers);
    }

    @Override
    public Double zincrby(String key, double score, String member) {
        return redisService.zincrby(key, score, member);
    }

    @Override
    public Long zcard(String key) {
        return redisService.zcard(key);
    }

    @Override
    public Long zinterstore(String dstkey, String... sets) {
        return redisService.zinterstore(dstkey, sets);
    }

    @Override
    public Set<String> zrange(String key, long start, long end) {
        return redisService.zrange(key, start, end);
    }

    @Override
    public Set<Tuple> zrangeWithScores(String key, long start, long end) {
        return redisService.zrangeWithScores(key, start, end);
    }

    @Override
    public Set<String> zrangeByScore(String key, double min, double max) {
        return redisService.zrangeByScore(key, min, max);
    }

    @Override
    public Set<Tuple> zrangeByScoreWithScores(String key, double min, double max) {
        return redisService.zrangeByScoreWithScores(key, min, max);
    }

    @Override
    public Long zrank(String key, String member) {
        return redisService.zrank(key, member);
    }

    @Override
    public Long zrem(String key, String... members) {
        return redisService.zrem(key, members);
    }

    @Override
    public Long zremrangeByRank(String key, long start, long end) {
        return redisService.zremrangeByRank(key, start, end);
    }

    @Override
    public Long zremrangeByScore(String key, double start, double end) {
        return redisService.zremrangeByScore(key, start, end);
    }

    @Override
    public ScanResult<Tuple> zscan(String key, String cursor) {
        return redisService.zscan(key, cursor);
    }

    @Override
    public Double zscore(String key, String member) {
        return redisService.zscore(key, member);
    }

    @Override
    public Long zunionstore(String dstkey, String... sets) {
        return redisService.zunionstore(dstkey, sets);
    }
}